'use strict'

/**
 * this file copy form webpack-dev-server
 * and modified for PSWP use.
 */
/* eslint no-param-reassign: 'off' */

const optionsSchema = require('./optionsSchema.json')

const indent = (str, prefix, firstLine) => {
  if (firstLine) {
    return prefix + str.replace(/\n(?!$)/g, `\n${prefix}`)
  }
  return str.replace(/\n(?!$)/g, `\n${prefix}`)
}

const getSchemaPart = (path, parents, additionalPath) => {
  parents = parents || 0
  path = path.split('/')
  path = path.slice(0, path.length - parents)
  if (additionalPath) {
    additionalPath = additionalPath.split('/')
    path = path.concat(additionalPath)
  }
  let schemaPart = optionsSchema
  for (let i = 1; i < path.length; i++) {
    const inner = schemaPart[path[i]]
    if (inner) { schemaPart = inner }
  }
  return schemaPart
}

const getSchemaPartText = (schemaPart, additionalPath) => {
  if (additionalPath) {
    for (let i = 0; i < additionalPath.length; i++) {
      const inner = schemaPart[additionalPath[i]]
      if (inner) { schemaPart = inner }
    }
  }
  while (schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref)
  let schemaText = OptionsValidationError.formatSchema(schemaPart) // eslint-disable-line
  if (schemaPart.description) { schemaText += `\n${schemaPart.description}` }
  return schemaText
}

class OptionsValidationError extends Error {
  constructor(validationErrors) {
    super()

    if (Error.hasOwnProperty('captureStackTrace')) { // eslint-disable-line
      Error.captureStackTrace(this, this.constructor)
    }
    this.name = 'PageSkeletonOptionsValidationError'

    this.message = `${'Invalid configuration object. ' +
   'page-skeleton-webpack-plugin has been initialised using a configuration object that does not match the API schema.\n'}${
      validationErrors.map(err => ` - ${indent(OptionsValidationError.formatValidationError(err), '   ', false)}`).join('\n')}`
    this.validationErrors = validationErrors
  }

  static formatSchema(schema, prevSchemas) {
    prevSchemas = prevSchemas || []

    const formatInnerSchema = (innerSchema, addSelf) => {
      if (!addSelf) return OptionsValidationError.formatSchema(innerSchema, prevSchemas)
      if (prevSchemas.indexOf(innerSchema) >= 0) return '(recursive)'
      return OptionsValidationError.formatSchema(innerSchema, prevSchemas.concat(schema))
    }

    if (schema.type === 'string') {
      if (schema.minLength === 1) { return 'non-empty string' } else if (schema.minLength > 1) { return `string (min length ${schema.minLength})` }
      return 'string'
    } else if (schema.type === 'boolean') {
      return 'boolean'
    } else if (schema.type === 'number') {
      return 'number'
    } else if (schema.type === 'object') {
      if (schema.properties) {
        const required = schema.required || []
        return `object { ${Object.keys(schema.properties).map((property) => {
          if (required.indexOf(property) < 0) return `${property}?`
          return property
        }).concat(schema.additionalProperties ? ['...'] : []).join(', ')} }`
      }
      if (schema.additionalProperties) {
        return `object { <key>: ${formatInnerSchema(schema.additionalProperties)} }`
      }
      return 'object'
    } else if (schema.type === 'array') {
      return `[${formatInnerSchema(schema.items)}]`
    }

    switch (schema.instanceof) {
      case 'Function':
        return 'function'
      case 'RegExp':
        return 'RegExp'
      default:
    }

    if (schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true)
    if (schema.allOf) return schema.allOf.map(formatInnerSchema).join(' & ')
    if (schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(' | ')
    if (schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(' | ')
    if (schema.enum) return schema.enum.map(item => JSON.stringify(item)).join(' | ')
    return JSON.stringify(schema, 0, 2)
  }

  static formatValidationError(err) {
    const dataPath = `configuration${err.dataPath}`
    if (err.keyword === 'additionalProperties') {
      return `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`
    } else if (err.keyword === 'oneOf' || err.keyword === 'anyOf') {
      if (err.children && err.children.length > 0) {
        return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}\n` +
     `Details:\n${err.children.map(e => ` * ${indent(OptionsValidationError.formatValidationError(e), '   ', false)}`).join('\n')}`
      }
      return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`
    } else if (err.keyword === 'enum') {
      if (err.parentSchema && err.parentSchema.enum && err.parentSchema.enum.length === 1) {
        return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`
      }
      return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`
    } else if (err.keyword === 'allOf') {
      return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`
    } else if (err.keyword === 'type') {
      switch (err.params.type) {
        case 'object':
          return `${dataPath} should be an object.`
        case 'string':
          return `${dataPath} should be a string.`
        case 'boolean':
          return `${dataPath} should be a boolean.`
        case 'number':
          return `${dataPath} should be a number.`
        case 'array':
          return `${dataPath} should be an array:\n${getSchemaPartText(err.parentSchema)}`
        default:
      }
      return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(err.parentSchema)}`
    } else if (err.keyword === 'instanceof') {
      return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}.`
    } else if (err.keyword === 'required') {
      const missingProperty = err.params.missingProperty.replace(/^\./, '')
      return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(err.parentSchema, ['properties', missingProperty])}`
    } else if (err.keyword === 'minLength' || err.keyword === 'minItems') {
      if (err.params.limit === 1) { return `${dataPath} should not be empty.` }
      return `${dataPath} ${err.message}`
    }
    // eslint-disable-line no-fallthrough
    return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${getSchemaPartText(err.parentSchema)}`
  }
}

module.exports = OptionsValidationError
